0X00 前言

做安全最困难的恐怕就是知识点过于杂碎,涉及面过于广阔,所以趁还记得,没事就总结完善备忘一下(参考一些资料,并加入一些自己使用过程中的理解),以防老了失忆,平时短路了拿出来查一下也是不错的。

0X01 基本语法

这一小节主要介绍的是一些增删改查的基本语法

(1)select

mysql 中 select 命令中允许不出现数据库或者数据表的名字,也就是可以没有 from ,这种情况下能将执行结果返回成一个单行单列的表格,例如 select now();返回当前时间。

(2)count()

select count(列) … 可以确定数据表中的记录条数

(3)limit offset,n

limit offset,n 指定输出从 offset 开始的 n 条数据 ,其中 offset 是从 0 开始的,例如 limit 2,2 表示跳过两条数据,从第三条数据开始输出第三第四条数据(offset 不写时,默认为0)

(4)order by

order by column 表示按照某一列对查询结果进行排序

(5)where/having

where/having 表示对行进行筛选,其中 where 的优先级高于 Having ,having 可以和 group by 配合使用,但是 where 不可以

(6)join

select table1.xxx,table2.xxx from table1 left join table2 on table1.yyy=table2.zzz/using(xxx); 是用来扩展查询结果的列的,两个表有键值是关联的,如果没有关联就相当于笛卡尔积,没什么太大的意义

下表是常用的一些语句模型

此处输入图片的描述

(7)union

union 实现的是行的扩展,它将两个查询的结果合并在一起,并要求两个查询的列个数相同,数据类型相同,如果不同,系统将会自动将 union 后面的数据的类型转换成前面的对应类型(union 是 mysql 中唯一的集合分隔符)

(8)group by

group by 配合如 count() sum() min() max() 等统计函数 计算有相同字段的行的另一些字段的统计数据,如果是想输出有相同字段的行的另一字段的所有数据,可以使用 group_concat() 这个统计函数,否则只能输出第一个,group by … with rollup 可以在结果的最后一行添加一个计算总和的行

(9)备份与还原数据表

备份:

create table newtable select * from oldtable;

还原:

delete from oldtable;
insert into oldtable select * from newtable;

(10)备份和还原数据库

使用外部工具 mysqldump

备份:

mysqldump -u loginame -p   dbname > backupfile

还原:

没有专门的恢复工具,我们直接使用下面的命令

mysql -u loginame -p   dbname < backupfile

或者我们直接就能在交互模式下重建

create database dbname;
use dbname;
source backupfile;

(11)插入数据

intert into tablename(column1,column2...) values (value1,value2...),(value3,value4...);

(12)更新数据

update tablename set column1=value1,column2=value2,...where columnID=ID;

(13)删除数据

delect from tablename where ID=ID;

(14)修改数据表

增加一个数据列:

alert table tablename add columnname clotype clooptions;

修改一个数据列:

alert table tablename change oldcolname newcolname coltype coloptions;

(15)information_schema 数据表家族

这是在 mysql5.0 以后引入的机制,有了这个机制,我们能在这些数据表中使用 select 命令检索有关数据库、数据表、数据列的元数据。

这是一个非常重要的机制,最明显的体现就在于使用 sqlmap 跑 MySQL 数据库的时候为什么那么方便,分分钟就能把所有的数据库和表列出来,有些名字甚至是很复杂的都没有丝毫的影响,但是如果你尝试跑 sql server 的数据库,你就会发现情况大不相同, sqlmap 会告诉你需要进行暴力破解,然后就会从他自带的字典中开始遍历,不仅时间非常的长,而且效果也很差,一些自定义的不常见的名字永远都不可能猜出来。

注意一下, information_schema 这个数据库是虚拟的,是在计算机上没有对应的文件的(我们知道数据库也是一个文件,都存储在计算机上,只不过不是纯文本文件罢了,有时候我们还能利用这个文件进行
getshell操作)

所以我们也不能直接使用 use information_schema 、show databases 、select * from information_schema.schemata 来查看有关信息

但是我们能通过 show tables from information_schema 、show columns from information_schema.columns 查看这个数据库内部的信息

mysql> select * from information_schema.schemata;
+--------------+--------------------+----------------------------+------------------------+----------+
| CATALOG_NAME | SCHEMA_NAME        | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH |
+--------------+--------------------+----------------------------+------------------------+----------+
| def          | information_schema | utf8                       | utf8_general_ci        | NULL     |
| def          | ctf                | utf8                       | utf8_general_ci        | NULL     |
| def          | employees          | utf8                       | utf8_general_ci        | NULL     |
| def          | mysql              | utf8                       | utf8_general_ci        | NULL     |
| def          | performance_schema | utf8                       | utf8_general_ci        | NULL     |
| def          | test               | latin1                     | latin1_swedish_ci      | NULL     |
| def          | world              | utf8                       | utf8_general_ci        | NULL     |
+--------------+--------------------+----------------------------+------------------------+----------+
7 rows in set (0.00 sec)

我们来看看这个数据库中有哪些表,它们分别对应着什么数据

此处输入图片的描述

还要补充的一点是,information_schema 数据表对所有用户开放而无视权限

0X02 解决方案

这一小节主要介绍的是一些一些实用的函数或者技巧

1.字符串操作

(1)合并字符串

concat(str1,str,str3...);
concat_ws(separator ,str1 ,str2 ,…);

(2)截取字符串

1.substring(str,pos,n);/substr(str,pos,n);/mid(str,pos,n);
mysql> select substring('www.baidu.com','5',5);
+----------------------------------+
| substring('www.baidu.com','5',5) |
+----------------------------------+
| baidu                            |
+----------------------------------+
1 row in set (0.00 sec)

注意:
(1)这里的 pos 是从 1 开始的,而不是从 0;
(2)n 不写时表示取到字符串末尾

2.letf(str,len); 返回从第一个字符开始的 len 个字符
3.right(str,len); 返回从最后一个字符开始的len 个字符
4.substring_index(str,delim,count);
mysql> select substring_index('www.baidu.com','.',1);
+----------------------------------------+
| substring_index('www.baidu.com','.',1) |
+----------------------------------------+
| www                                    |
+----------------------------------------+
1 row in set (0.00 sec)

mysql> select substring_index('www.baidu.com','.',2);
+----------------------------------------+
| substring_index('www.baidu.com','.',2) |
+----------------------------------------+
| www.baidu                              |
+----------------------------------------+
1 row in set (0.00 sec)

mysql> select substring_index('www.baidu.com','.',-2);
+-----------------------------------------+
| substring_index('www.baidu.com','.',-2) |
+-----------------------------------------+
| baidu.com                               |
+-----------------------------------------+
1 row in set (0.10 sec)

(3)计算字符串长度

char_length()/character_length() 和 length()/octet_length()

注意: char_length()/character_length() 计算的是字符的个数,而length()/octet_length() 计算的是字节的长度

但是我在测试的时候发现即使使用中文两者的结果还是一样的,都是按照字节去计算的,可能需要进一步考证

mysql> select LENGTH('你好');
+----------------+
| LENGTH('你好')     |
+----------------+
|              4 |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select CHAR_LENGTH('你好');
+---------------------+
| CHAR_LENGTH('你好')     |
+---------------------+
|                   4 |
+---------------------+
1 row in set, 1 warning (0.00 sec)

(4)改变存储的字符串

将 colname 列的双引号换成单引号

replace(colname,'"','\'');

(5)判断子字符串在字符串中出现的位置

1.locate()

返回子串 substr 在字符串 str 中第一次出现的位置。如果子串 substr 在 str 中不存在,返回值为 0:

LOCATE(substr,str);

例如:

mysql> SELECT LOCATE('bar', ‘foobarbar'); 
-> 4 
mysql> SELECT LOCATE('xbar', ‘foobar'); 
-> 0 

返回子串 substr 在字符串 str 中的第 pos 位置后第一次出现的位置。如果 substr 不在 str 中返回 0 :

LOCATE(substr,str,pos) 

例如:

mysql> SELECT LOCATE('bar', ‘foobarbar',5); 
-> 7 
2.instr()

在一个字符串 str 中搜索指定的字符 substr,返回其第一次出现的位置 index ,其中 index 从 1 开始,如果不存在返回 0 ;

instr(str,substr);

例如:

instr('123', '12');
->1  

instr('123', '23');
->2
3.position()

是 locate() 的别名,用法完全一样

4.find_in_set()
FIND_IN_SET(str,strlist)
  1. 假如字符串str在由N子链组成的字符串列表strlist中,则返回值的范围在1到N之间。

  2. 一个字符串列表就是一个由一些被‘,’符号分开的自链组成的字符串。

  3. 如果第一个参数是一个常数字符串,而第二个是typeSET列,则FIND_IN_SET()函数被优化,使用比特计算。

  4. 如果str不在strlist或strlist为空字符串,则返回值为0。

  5. 如任意一个参数为NULL,则返回值为NULL。这个函数在第一个参数包含一个逗号(‘,’)时将无法正常运行。

注:strlist:一个由英文逗号“,”链接的字符串,例如:”a,b,c,d”,该字符串形式上类似于SET类型的值被逗号给链接起来。

例如:

SELECT FIND_IN_SET('b','a,b,c,d'); //返回值为2,即第2个值

(6)正则匹配

1.like

like 常用通配符:% 、_ 、escape

% : 匹配0个或任意多个字符

_ : 匹配任意一个字符

escape : 转义字符,可匹配%和_。如SELECT * FROM table_name WHERE column_name LIKE '/%/_%_' ESCAPE'/'
2.regexp/rlike

rlike和regexp:常用通配符:. 、* 、 [] 、 ^ 、 $ 、{n}

. : 匹配任意单个字符

* : 匹配0个或多个前一个得到的字符

[] : 匹配任意一个[]内的字符,[ab]*可匹配空串、a、b、或者由任意个a和b组成的字符串。

^ : 匹配开头,如^s匹配以s或者S开头的字符串。

$ : 匹配结尾,如s$匹配以s结尾的字符串。

{n} : 匹配前一个字符反复n次。

注意:

(1)rlike 和 regexp 并不是完全匹配,而是只要模板匹配字符串的一部分就可以了,但是 like 是完全匹配
(2) Like 和 rlike 和 regexp 都是不区分大小写的

3.字符串的二进制比较

我们想要区分大小写就要通过这种二进制比较的方式

mysql> select "A" = "a";
+-----------+
| "A" = "a" |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

mysql> select "A" = BINARY "a";
+------------------+
| "A" = BINARY "a" |
+------------------+
|                0 |
+------------------+
1 row in set (0.10 sec)

(7)自定义排序

1.elt()
ELT(N ,str1 ,str2 ,str3 ,…)

函数使用说明:若 N = 1 ,则返回值为 str1 ,若 N = 2 ,则返回值为 str2 ,以此类推。 若 N 小于 1 或大于参数的数目,则返回值为 NULL 。 ELT() 是 FIELD() 的补数

mysql> SELECT ELT(3,'hello','halo','test','world');
+--------------------------------------+
| ELT(3,'hello','halo','test','world') |
+--------------------------------------+
| test                                 |
+--------------------------------------+
1 row in set

盲注中我们可以这样将其作为开关

mysql> select ELT(1,1);
+----------+
| ELT(1,1) |
+----------+
| 1        |
+----------+
1 row in set (0.00 sec)

mysql> select ELT(1,0);
+----------+
| ELT(1,0) |
+----------+
| 0        |
+----------+
1 row in set (0.00 sec)
2.field()
FIELD(str, str1, str2, str3, ……)

order by field(str,str1,str2,str3,str4……),str与str1,str2,str3,str4比较,其中str指的是字段名字,

意为:字段str按照字符串str1,str2,str3,str4的顺序返回查询到的结果集。如果表中str字段值不存在于str1,str2,str3,str4中的记录,放在结果集最前面返回。

假如说表是这样的:

root@localhost|iris>select * from ta;
+----+--------+------+-------+
| id | name   | age  | class |
+----+--------+------+-------+
|  1 | iris   |   11 | a1    |
|  2 | iris   |   22 | a2    |
|  3 | seiki  |   33 | a3    |
|  4 | seiki  |   44 | a4    |
|  5 | xuding |   55 | a5    |
|  6 | xut    |   66 | a6    |
|  7 | iris   |   12 | a2    |
|  8 | iris   |   24 | a4    |
|  9 | seiki  |   36 | a6    |
| 10 | seiki  |   48 | a8    |
| 11 | xuding |   50 | a0    |
| 12 | xut    |   77 | a7    |
+----+--------+------+-------+
12 rows in set (0.00 sec)

按照’seiki’,’iris’,’xut’来排序,结果如下:

root@localhost|iris>select * from ta order by field(name,'seiki','iris','xut');
+----+--------+------+-------+
| id | name   | age  | class |
+----+--------+------+-------+#不在str1,str2,str3中的内容,放在最前面返回,str值相同按照主键的顺序
|  5 | xuding |   55 | a5    |
| 11 | xuding |   50 | a0    |
|  3 | seiki  |   33 | a3    |
|  4 | seiki  |   44 | a4    |
|  9 | seiki  |   36 | a6    |
| 10 | seiki  |   48 | a8    |
|  1 | iris   |   11 | a1    |
|  2 | iris   |   22 | a2    |
|  7 | iris   |   12 | a2    |
|  8 | iris   |   24 | a4    |
|  6 | xut    |   66 | a6    |
| 12 | xut    |   77 | a7    |
+----+--------+------+-------+
12 rows in set (0.00 sec)

当然这是正常的用法,但是其实还可以这样理解,这个函数返回的是str 在后面这些字符串中的索引

mysql> SELECT FIELD('halo','hello','halo','test','world');
+---------------------------------------------+
| FIELD('halo','hello','halo','test','world') |
+---------------------------------------------+
|                                           2 |
+---------------------------------------------+
1 row in set

其实我们还可以把这个函数看成是一个开关,或许在盲注的时候能代替 if ,例如下面这样

mysql> select FIELD(1,0);
+------------+
| FIELD(1,0) |
+------------+
|          0 |
+------------+
1 row in set (0.00 sec)

mysql> select FIELD(1,1);
+------------+
| FIELD(1,1) |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

这里的第二个参数决定了这条语句的执行结果,和 if 还是比较类似的,有时候出现 NULL 的时候还可以配合 isnull() 这个函数将其转换成 布尔值,这里提一下,说不定也能用的到。

2.变量与条件表达式

(1)变量

mysql 5.0 以后开始支持存储过程,变量是存储过程中比较重要的元素,MySQL 的变量分为三个类型

1.变量类型:

1.普通变量: 以@开头,在 sql 连接关闭时失去内容
2.系统变量: 以@@开头,表示的是mysql 服务器的工作状态和属性,许多系统变量有两种形式,一种表示当前连接 @@session.wait_timeout,另一种表示mysql 服务器 @@global.wait_timeout

mysql> select @@session.wait_timeout;
+------------------------+
| @@session.wait_timeout |
+------------------------+
|                  28800 |
+------------------------+
1 row in set (0.00 sec)

mysql> select @@global.wait_timeout;
+-----------------------+
| @@global.wait_timeout |
+-----------------------+
|                 28800 |
+-----------------------+
1 row in set (0.00 sec)

3.存储过程的局部变量: 没有特殊的标志,只在存储过程内有效

2.变量赋值:

变量赋值有两种方式

1.set 赋值的时候 使用 =
2.select 赋值的时候使用 := 或者是 into

mysql> set @varname = 3;
Query OK, 0 rows affected (0.00 sec)

mysql> select @varname;
+----------+
| @varname |
+----------+
|        3 |
+----------+
1 row in set (0.00 sec)

mysql> select @varname := 4;
+---------------+
| @varname := 4 |
+---------------+
|             4 |
+---------------+
1 row in set (0.00 sec)

mysql> select @varname;
+----------+
| @varname |
+----------+
|        4 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from bsqli into @varname;
Query OK, 1 row affected (0.32 sec)

mysql> select @varname;
+----------+
| @varname |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)
2.变量赋值:

我们可以使用表达式给变量赋值,请看下面的实验

mysql> select ID ,@rownum:=@rownum+1 as rownum from city order by ID limit 10;
+----+--------+
| ID | rownum |
+----+--------+
|  1 |     11 |
|  2 |     12 |
|  3 |     13 |
|  4 |     14 |
|  5 |     15 |
|  6 |     16 |
|  7 |     17 |
|  8 |     18 |
|  9 |     19 |
| 10 |     20 |
+----+--------+
10 rows in set (0.00 sec)

这样就实现了逐个增加,是不是很神奇?我们再来看下面的测试

mysql> select @b:=@b is not null;
+--------------------+
| @b:=@b is not null |
+--------------------+
|                  0 |
+--------------------+
1 row in set (0.00 sec)

mysql> select @b:=@b is not null;
+--------------------+
| @b:=@b is not null |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

这个怎么理解呢?我们先来看赋值号后面的这个表达式

@b is not null

由于 @b 并没有在当前的会话中定义过,于是一开始是 null ,然后我们做了个判断,判断 @b 不是 null ,很明显这个得到的是假,也就是 0 ,那么再执行完这个语句之后 @b 就被赋值为 0 ,当第二次再执行的时候就是判断 0 不是 Null ,这次肯定是真,于是返回1 此时 @b 就被赋值为1

这样就达到了在同一个会话中,相同的语句执行结果不同的效果,之前在 LCTF2018 中有一道题就是利用这种方式去做的,但是没人解出来。

(2)条件表达式

1.if(condition,result1,result2);

当 condition 结果为真时返回 result1 否则返回 result2,这常常用作我们盲注的开关函数,

mysql> select if(1,1,0);
+-----------+
| if(1,1,0) |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

mysql> select if(0,1,0);
+-----------+
| if(0,1,0) |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)
2.case(…)when…then…else…end

case 有两种变体

(1)case expr when val1 then result1 when val2 then result2 else result3 end;

就是会判断 expr 的结果 ,根据结果是 val1 还是 val2 或者其他,来执行不同的语句

(2)case when condition1 then result1 when condition2 result2 else result3 end;

这种就是不判断值,直接根据不同情况进行选择

比如可以构造这样一条注入语句

select * from test where id =-1 union select 1,case when username like 'a%' then 0 else 2222222222222222222 end,3,4 from tdb_admin

3.子查询

(1)模板一

select ... from ... where col = [any|all](select...);

1.在这个变体里,子查询的返回结果必须是一个离散值(一行一列),用来和 col 进行比较操作,这里的比较操作符=还可以是 > >= < <= <>

2.当前面有修饰符的时候,前面的修饰符可以是 any/some( 这两个同义)和 all,这时子查询可以返回多个值,col = any… 等同于 col = in … ,当前面的修饰符是 all 的时候只有两种情况能让 operator all 的返回结果为真,一种是在所有的值都能满足这个 operator 条件时,另一种是在 select 子查询没有任何返回结果时

(2)模板二

select ....from ... where col [not]in(select...);

此时select 子查询的返回结果可以是一个离散值的列表

(3)模板三

select row(value1,valu2...) = [any/some](select col1,col2...);

该语句查询数据表中师傅存在一条对应的记录,子查询返回的结果是一组离散值,但是当使用 any 或者其同义词 some 修饰的时候可以返回多组离散值,其中只要有一组满足条件结果则为真

(4)模板四

select ... from ...where col [not]exists(select...);

(5)模板五

select ... from (select ...) as name where ...

外层 select 查询的是内层 select 查询出来的临时表,MySQL 要求这种临时表必须通过 as name 的形式给予别名

注意: mysql 不允许在子查询中使用 limit ,limit 一定是在最外层并且最后运行的

(6)补充

当然除了上面这几种类别以外,子查询的位置还能放在 select 后面作为选择列,或者放在 order by 后面作为拍序列,但是在 sql 注入中子查询的缺点是不能回显,union 倒是可以,只要有回显位,但是现在能回显的越来越少了,一般都是盲注什么的。

0X03 其他细节

(1)命名规则:

数据库、数据表、数据字段等数据库对象的名字长度最多达到64字符,允许使用的字符是 MySQL 允许使用的字符集中的所有数字字母符号以及 _ 和 $

注意:
(1)包含特殊字符和保留字的名字一般不被使用,但是还是可以通过添加 反引号的方式来使用,但是同样这样的字段是没法直接用命令行查询的,查询的时候也要带上反引号
(2)当使用不在该数据库的数据表的时候,要使用 数据库.数据表 的形式,同理,如果遇到不能区分的列,也要使用类似的方式进行区分

(2)字符串

字符串可以用单引号包裹也可以用双引号,当出现引号里面还有引号的使用,必须使用不同的引号加以区分,或者使用反斜杠进行转义

MySQL 支持使用 16进制值表示对象

(3)数值

1.MySQL 支持十进制使用小数点,并且可以使用 用e 表示的科学计数法表示很大或者很小的数值

2.MYSQL 支持使用 0x 或者 x’12121’ 表示16进制数据

mysql> select 0x4142434445464748494a;
+------------------------+
| 0x4142434445464748494a |
+------------------------+
| ABCDEFGHIJ             |
+------------------------+
1 row in set (0.00 sec)

mysql> select x'4142434445464748494a';
+-------------------------+
| x'4142434445464748494a' |
+-------------------------+
| ABCDEFGHIJ              |
+-------------------------+
1 row in set (0.00 sec)

3.MySQL 支持进制之间的隐式转换,比如 16进制加一个十进制的数会变成十进制

mysql> select 0x41;
+------+
| 0x41 |
+------+
| A    |
+------+
1 row in set (0.00 sec)

mysql> select 0x41+0;
+--------+
| 0x41+0 |
+--------+
|     65 |
+--------+
1 row in set (0.00 sec)

4.MySQL 支持字符串和数值之间的转换,字符串在和数值运算时会将字符串转换成数值(开头部分是数值则转化成对应数值,如果不是数值则自动转化成0)

mysql> select "112.3456asdasd"+1;
+--------------------+
| "112.3456asdasd"+1 |
+--------------------+
|           113.3456 |
+--------------------+
1 row in set, 1 warning (0.00 sec)

5.MySQL 5.03 开始支持二进制数值,可以表示为 b’100110’ 的形式

(4)注释

1.单行注释:# 和 – (后面有一个空格)
2.多行注释:/*/
3.内联注释:`/
!*/`

(5)操作符

此处输入图片的描述
此处输入图片的描述

注意:逻辑与的优先级要高于逻辑或

(6)一些函数

1.比较、测试、分支

此处输入图片的描述

2.字符串处理

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

3.统计函数(与 group by 配合使用)

此处输入图片的描述

4.算数函数

此处输入图片的描述

此处输入图片的描述

5.其他函数

此处输入图片的描述

此处输入图片的描述

6.mysql 运算符的优先级

此处输入图片的描述

0X04 总结

因为最近心血来潮想写一点关于 SQL 的东西,中间一些知识点不想再过多介绍,于是就打算偷个小懒先把这篇老文章丢出来,到时候直接参考。

0X05 参考链接

https://www.cnblogs.com/xiaoxi/p/5889486.html
https://www.cnblogs.com/tommy-huang/p/4483583.html
https://blog.csdn.net/dongganen/article/details/78580813
https://www.cnblogs.com/xdans/p/5412468.html